﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Sio.Mdm.Server;
using Sio.Mdm.Types;
using System.Linq.Expressions;
using System.Reflection;

namespace Sio.Mdm.Entities
{
    public class EntityCollection : IList, IEntityCollection
    {
        #region Fields
        /// <summary>
        /// Acctual list storing entities
        /// </summary>
        internal List<IEntity> InnerList = new List<IEntity>();
        /// <summary>
        /// Optional owner of collection
        /// </summary>
        private IEntity Owner; 
        #endregion

        #region Construction
        /// <summary>
        /// Construct collection for specified entity type.
        /// </summary>
        /// <param name="entityType">Any <see cref="IEntity"/> type to use for entity template</param>
        /// <exception cref="ArgumentException">
        /// <paramref name="entityType"/> is not of <see cref="IEntity"/>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="entityType"/> is null
        /// </exception>
        public EntityCollection(Type entityType)
            : this(entityType, null)
        {
        }
        /// <summary>
        /// Construct collection for specified entity type with specified entity
        /// as owner
        /// </summary>        
        /// <param name="owner">Instance of entity that is owner of collection</param>
        /// <param name="entityType">Any <see cref="IEntity"/> type to use for entity template</param>
        /// <exception cref="ArgumentException">
        /// <paramref name="entityType"/> is not of <see cref="IEntity"/>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="entityType"/> is null
        /// </exception>
        public EntityCollection(Type entityType, IEntity owner)
        {
            if (null == entityType)
            {
                throw new ArgumentNullException("entityType");
            }

            if (!typeof(IEntity).IsAssignableFrom(entityType))
            {
                throw new ArgumentException("Type must implement IEntity", "entityType");
            }

            EntityType = entityType;
            Owner = owner;
        }        
        #endregion

        #region Properties
        protected EntityFactory.EntityInfo Info
        {
            get
            {
                return EntityFactory.EntitiesInfo[EntityType];
            }
        }
        private Type _entityType;
        /// <summary>
        /// Type of entity stored
        /// </summary>        
        public Type EntityType
        {
            get
            {
                return _entityType;
            }
            private set
            {
                _entityType = value;
            }
        }
        private int _totalCount = 0;
        public virtual int TotalCount
        {
            get
            {
                return _totalCount;
            }
            set
            {
                _totalCount = value;
            }
        }
        #endregion

        #region IEntity Members
        string[] IEntity.FieldsName
        {
            get
            {
                return new string[0];
            }
        }
        public EntityState EntityState
        {
            get
            {
                if (_error != null)
                {
                    return EntityState.Error;
                }

                EntityState state = EntityState.Unknown;

                foreach (IEntity entity in this)
                {
                    if (entity.EntityState != EntityState.Unchanged &&
                        entity.EntityState != EntityState.Unknown)
                    {
                        state = EntityState.Changed;
                        break;
                    }
                }

                return state;
            }
        }
        protected Result _error;
        public Result Error
        {
            get
            {
                return _error;
            }
        }
        bool IEntity.Create()
        {
            return false;
        }
        bool IEntity.Delete()
        {
            return false;
        }
        Dictionary<string, object> IEntity.GetOldValues()
        {
            return null;
        }
        
        bool IEntity.Load<T>(ISioFilter<T> criteria)
        {
            return false;
        }

        public bool Update()
        {
            EntityCollection reference = this;            
            _error = Info.Update(ref reference);
            return _error == null;
        }
        public void RemoveAll()
        {
            foreach (IEntity entity in this)
            {
                if( entity.EntityState == EntityState.Error ||
                    entity.EntityState == EntityState.Unknown)
                {
                    continue;
                }

                entity.SetState(EntityState.Deleted);
            }
        }
        public bool Validate()
        {
            return false;
        }
        void IEntity.SetField(string name, object value)
        {   
        }
        object IEntity.GetField(string name)
        {
            return null;
        }
        T IEntity.GetField<T>(string name)
        {
            return default(T);
        }
        void IEntity.SetState(EntityState state)
        {   
        }
        #endregion

        #region IList Members

        public int Add(object value)
        {
            InnerList.Add(value as IEntity);
            return Count;
        }

        public void Clear()
        {
            InnerList.Clear();
        }

        public bool Contains(object value)
        {
            return InnerList.Contains(value as IEntity);
        }

        public int IndexOf(object value)
        {
            return InnerList.IndexOf(value as IEntity);
        }

        public void Insert(int index, object value)
        {
            InnerList.Insert(index, value as IEntity);
        }

        public bool IsFixedSize
        {
            get 
            {
                return false;
            }
        }

        public bool IsReadOnly
        {
            get 
            {
                return false;
            }
        }

        public void Remove(object value)
        {
            InnerList.Remove(value as IEntity);
        }

        public void RemoveAt(int index)
        {
            InnerList.RemoveAt(index);
        }

        public object this[int index]
        {
            get
            {
                return InnerList[index];
            }
            set
            {
                InnerList[index] = value as IEntity;
            }
        }

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            InnerList.CopyTo(array as IEntity[], index);
        }

        public int Count
        {
            get 
            {
                return InnerList.Count;
            }
        }

        public bool IsSynchronized
        {
            get 
            {
                return false;
            }
        }

        public object SyncRoot
        {
            get 
            {
                return null;
            }
        }

        #endregion

        #region IEnumerable Members
        public IEnumerator GetEnumerator()
        {
            return InnerList.GetEnumerator();
        }
        #endregion

        #region IEntityCollection Members
        public IEntityCollection GetChanges()
        {
            EntityCollection result = new EntityCollection(EntityType);

            foreach (IEntity entity in this)
            {
                if (entity.EntityState != EntityState.Unchanged &&
                    entity.EntityState != EntityState.Unknown)
                {
                    result.Add(entity);
                }
            }

            return result;
        }
        public IEntityCollection GetChanges(EntityState state)
        {
            EntityCollection result = new EntityCollection(EntityType);

            foreach (IEntity entity in this)
            {
                if (entity.EntityState != state)
                {
                    result.Add(entity);
                }
            }

            return result;
        }
        #endregion
    }

    /// <summary>
    /// Represent collection of entity instances
    /// </summary>
    /// <typeparam name="T">Of of <see cref="IEntity"/> derived interface</typeparam>
    public class EntityCollection<T> : EntityCollection, IList<T> where T: IEntity
    {        
        #region Construction
        public EntityCollection()
            : base(typeof(T))
        {
        }
        public EntityCollection(IEntity owner)
            : base(typeof(T), owner)
        {
        }

        public EntityCollection(ISioFilter<T> expression)
            : base(typeof(T))
        {
            Select(expression);
        }
        #endregion

        #region Properties

        #region LastExpression
        private ISioFilter<T> _lastExpression = null;
        public ISioFilter<T> LastExpression
        {
            get
            {
                return _lastExpression;
            }
            set
            {
                if (_lastExpression == value)
                {
                    return;
                }

                _lastExpression = value;
                TotalCount = -1;
            }
        }
        #endregion

        #endregion

        #region Methods

        #region Public

        #region IList<T> Members
        public int IndexOf(T item)
        {
            return base.IndexOf(item);
        }
        public void Insert(int index, T item)
        {
            base.Insert(index, item);
        }        
        public new T this[int index]
        {
            get
            {
                return (T)base[index];
            }
            set
            {
                base[index] = value;
            }
        }
        #endregion

        #region ICollection<T> Members
        public void Add(T item)
        {
            base.Add(item);
        }        
        public bool Contains(T item)
        {
            return base.Contains(item);
        }
        public void CopyTo(T[] array, int arrayIndex)
        {
            base.CopyTo(array, arrayIndex);
        }        
        public bool Remove(T item)
        {
            base.Remove(item);
            return true;
        }
        #endregion

        #region IEnumerable<T> Members
        public new IEnumerator<T> GetEnumerator()
        {
            return new Enumerator<T>(this.InnerList.GetEnumerator());
        }
        #endregion

        #region Select
        public bool Select(Expression query)
        {
            return Select(new Filter<T>(query));
        }
        public bool Select(ISioFilter<T> query)
        {
            return Select(query, 0, Int32.MaxValue);
        }
        public bool Select(ISioFilter<T> query, int start, int count)
        {
            _lastExpression = query;
            object[] args = new object[] { this, query.Skip(start).Take(count), Session.Current };

            _error = Info.InvokeMethod(
                    "Select",
                    Type.EmptyTypes,
                    new Type[] { GetType().MakeByRefType(), typeof(ISioFilter<>), typeof(Session) },
                    ref args
            );

            if (_error == null)
            {
                Clear();
                foreach (IEntity entity in args[0] as EntityCollection)
                {
                    Add(entity);
                    entity.SetState(EntityState.Unchanged);
                }
            }

            TotalCount = GetTotalCount();

            return true;
        }        
        #endregion

        #endregion

        #region Private
        private int GetTotalCount()
        {
            int count = 0;
            object[] args = new object[] { count, LastExpression, Session.Current };
            Type[] types = new Type[] { typeof(int).MakeByRefType(), typeof(ISioFilter<>), typeof(Session) };

            _error = Info.InvokeMethod("Count", Type.EmptyTypes, types, ref args);

            return (int) args[0];
        }
        private int GetTotalCount<T1>()
        {
            int count = 0;
            object[] args = new object[] { count, LastExpression, Session.Current };
            Type[] types = new Type[] { typeof(int).MakeByRefType(), typeof(Expression<Func<T, T1, bool>>), typeof(Session) };
            Type[] generics = new Type[] { typeof(T1) };

            _error = Info.InvokeMethod("CountWithJoin", generics, types, ref args);

            return (int) args[0];
        }
        private int GetTotalCount<T1, T2>()
        {
            int count = 0;
            object[] args = new object[] { count, LastExpression, Session.Current };
            Type[] types = new Type[] { typeof(int).MakeByRefType(), typeof(Expression<Func<T, T1, T2, bool>>), typeof(Session) };
            Type[] generics = new Type[] { typeof(T1), typeof(T2) };

            _error = Info.InvokeMethod("CountWithJoin", generics, types, ref args);

            return (int)args[0];
        }
        private int GetTotalCount<T1, T2, T3>()
        {
            int count = 0;
            object[] args = new object[] { count, LastExpression, Session.Current };
            Type[] types = new Type[] { typeof(int).MakeByRefType(), typeof(Expression<Func<T, T1, T2, T3, bool>>), typeof(Session) };
            Type[] generics = new Type[] { typeof(T1), typeof(T2), typeof(T2) };

            _error = Info.InvokeMethod("CountWithJoin", generics, types, ref args);

            return (int)args[0];
        }
        #endregion

        #endregion

        #region Enumerator
        [Serializable]
        public struct Enumerator<Type> : IEnumerator<Type>, IDisposable, IEnumerator where Type: IEntity
        {
            private IEnumerator InnerEnumerator;
            internal Enumerator(IEnumerator enumerator)
            {
                InnerEnumerator = enumerator;
            }

            public Type Current
            {
                get
                {
                    return (Type)InnerEnumerator.Current;
                }
            }

            public void Dispose()
            {
                InnerEnumerator = null;
            }
            public bool MoveNext()
            {
                return InnerEnumerator.MoveNext();
            }

            #region IEnumerator Members

            object IEnumerator.Current
            {
                get
                {
                    return InnerEnumerator.Current;
                }
            }
            public void Reset()
            {
                InnerEnumerator.Reset();
            }

            #endregion
        }
        #endregion
    }
}
